using System;
using System.Collections.Generic;
using System.Text;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using Microsoft.Xna.Framework;
using BoOnce.GameLogic;
using FarseerGames.FarseerPhysics;
using BoOnce.Xml;

namespace BoOnce.Physic
{
    class PhysicComponent_MultiSimulator : IPhysicComponent
    {
        #region Attributes
        private const Enums.CollisionCategories NO_GROUND_CATEGORY = Enums.CollisionCategories.Cat31;


        private Body DEFAULT_BODY;
        private Geom DEFAULT_GEOM;

        private Ground[] _Grounds;
        private int _NbGrounds;
        private PhysicsSimulator[] _PhysicsSimulators;

        private float EPSILON = 0.02f;
        private const float GROUND_MASS = 1f;
        private const float GROUND_START_X = 0f;

        private float SPACE_BETWEEN_GROUND;
        private int NB_SIMULATOR;
        private int SIMULATOR_RANGE;
        private float GROUND_HEIGHT;
        private float GROUND_WIDTH;
        private float GROUND_START_Y;
        #endregion

        #region Constructor
        public PhysicComponent_MultiSimulator(Game game, PhysicComponentConfigXml config)
        {
            GROUND_START_Y = config.GroundStartY;
            GROUND_WIDTH = config.GroundWidth;
            GROUND_HEIGHT = config.GroundHeight;
            NB_SIMULATOR = config.NbSimulator;
            SIMULATOR_RANGE = config.SimulatorRange;
            SPACE_BETWEEN_GROUND = config.SpaceBetweenGround;

            DEFAULT_BODY = BodyFactory.Instance.CreateRectangleBody(GROUND_WIDTH, GROUND_HEIGHT, GROUND_MASS);
            DEFAULT_GEOM = GeomFactory.Instance.CreateRectangleGeom(DEFAULT_BODY, GROUND_WIDTH, GROUND_HEIGHT);

            _Grounds = new Ground[(NB_SIMULATOR * SIMULATOR_RANGE) + 1];
            _NbGrounds = _Grounds.Length - 1;
            _PhysicsSimulators = new PhysicsSimulator[NB_SIMULATOR];
            for (int i = 0; i < NB_SIMULATOR; i++)
            {
                _PhysicsSimulators[i] = new PhysicsSimulator(config.Gravity);
                //_PhysicsSimulators[i].FrictionType = FrictionType.Average;
                //_PhysicsSimulators[i].AllowedPenetration = 0.0f;
                _PhysicsSimulators[i].MaxContactsToDetect = 10;
                _PhysicsSimulators[i].MaxContactsToResolve = 10;
                _PhysicsSimulators[i].AllowedPenetration = 0.0f;
                CreateGrounds(_PhysicsSimulators[i], i);
            }
        }
        #endregion

        #region Private Functions
        private void CreateGrounds(PhysicsSimulator sim,int simIndex)
        {
            int decal = simIndex*SIMULATOR_RANGE;
            for (int i = 0; i < SIMULATOR_RANGE; i++)
            {
                Body body = new Body(DEFAULT_BODY);
                Geom geom = new Geom(body, DEFAULT_GEOM);

                body.Position = new Vector2(GROUND_START_X, i + decal + GROUND_START_Y);
                body.IsStatic = true;
                geom.CollisionCategories = IntToCat(i + 1);
                geom.CollidesWith = Enums.CollisionCategories.None;
                geom.CollisionResponseEnabled = true;
                geom.CollisionEnabled = true;

                sim.Add(geom);
                sim.Add(body);

                _Grounds[i + decal] = new Ground(sim, geom.CollisionCategories, body, geom, (float)(i + decal + GROUND_START_Y) /*- (GROUND_HEIGHT / 2f)*/, simIndex, i + decal);
            }
        }

        private Enums.CollisionCategories IntToCat(int i)
        {
            switch (i)
            {
                case 1:
                    return Enums.CollisionCategories.Cat1;
                case 2:
                    return Enums.CollisionCategories.Cat2;
                case 3:
                    return Enums.CollisionCategories.Cat3;
                case 4:
                    return Enums.CollisionCategories.Cat4;
                case 5:
                    return Enums.CollisionCategories.Cat5;
                case 6:
                    return Enums.CollisionCategories.Cat6;
                case 7:
                    return Enums.CollisionCategories.Cat7;
                case 8:
                    return Enums.CollisionCategories.Cat8;
                case 9:
                    return Enums.CollisionCategories.Cat9;
                case 10:
                    return Enums.CollisionCategories.Cat10;
                case 11:
                    return Enums.CollisionCategories.Cat11;
                case 12:
                    return Enums.CollisionCategories.Cat12;
                case 13:
                    return Enums.CollisionCategories.Cat13;
                case 14:
                    return Enums.CollisionCategories.Cat14;
                case 15:
                    return Enums.CollisionCategories.Cat15;
                case 16:
                    return Enums.CollisionCategories.Cat16;
                case 17:
                    return Enums.CollisionCategories.Cat17;
                case 18:
                    return Enums.CollisionCategories.Cat18;
                case 19:
                    return Enums.CollisionCategories.Cat19;
                case 20:
                    return Enums.CollisionCategories.Cat20;
                case 21:
                    return Enums.CollisionCategories.Cat21;
                case 22:
                    return Enums.CollisionCategories.Cat22;
                case 23:
                    return Enums.CollisionCategories.Cat23;
                case 24:
                    return Enums.CollisionCategories.Cat24;
                case 25:
                    return Enums.CollisionCategories.Cat25;
                case 26:
                    return Enums.CollisionCategories.Cat26;
                case 27:
                    return Enums.CollisionCategories.Cat27;
                case 28:
                    return Enums.CollisionCategories.Cat28;
                case 29:
                    return Enums.CollisionCategories.Cat29;
                case 30:
                    return Enums.CollisionCategories.Cat30;
                case 31:
                    return Enums.CollisionCategories.Cat31;
                default:
                    return Enums.CollisionCategories.All;
            }
        }

        private Ground ChangeGround(Body body, Geom geom, Ground currentGround, int decal)
        {
            if (currentGround.GroundIndex + decal >= _NbGrounds)
            {
                decal = _NbGrounds - currentGround.GroundIndex - 1;
            }
            if (currentGround.GroundIndex + decal < 0)
            {
                decal = -(currentGround.GroundIndex + decal);
            }

            Ground newGround = _Grounds[currentGround.GroundIndex + decal];

            if (newGround != currentGround)
            {
                // set collision with the new ground
                geom.CollidesWith = (newGround.Category | NO_GROUND_CATEGORY);

                // need to change simulator ?
                PhysicsSimulator oldSim = currentGround.Simulator;
                PhysicsSimulator newSim = newGround.Simulator;
                if (oldSim != newSim)
                {
                    oldSim.Remove(body);
                    oldSim.Remove(geom);
                    newSim.Add(body);
                    newSim.Add(geom);
                }
            }

            return newGround;
        }

        private Ground GetGround(float y, float height)
        {
            int i = (int)(y - GROUND_START_Y + 1f + (height / 2.0f));
            return _Grounds[i];
        }
        #endregion

        #region IPhysicComponent Membres
        public void Update(GameTime gameTime)
        {
            foreach (PhysicsSimulator simulator in _PhysicsSimulators)
            {
                simulator.Update(gameTime.ElapsedGameTime.Milliseconds * .001f);
            }
        }

        public void Initialize() { }

        public PhysicsSimulator[] Simulators { get { return _PhysicsSimulators; } }

        public Ground AddPhysicalEntity(Body body, Geom geom, float height)
        {
            // Get ground
            Ground ground = GetGround((int)geom.Position.Y, height);
            // Set categories
            geom.CollisionCategories = NO_GROUND_CATEGORY;
            geom.CollidesWith = (NO_GROUND_CATEGORY | ground.Category);

            // Add to simulator
            ground.Simulator.Add(body);
            ground.Simulator.Add(geom);

            return ground;
        }

        public void Remove(Body body, Geom geom)
        {
            foreach (PhysicsSimulator simulator in _PhysicsSimulators)
            {
                simulator.Remove(body);
                simulator.Remove(geom);
            }
        }

        public Ground NotifyMove(Vector2 newPos,Ground currentGround, Body body, Geom geom, float height, ref float accumulator)
        {
            Ground ground = currentGround;
            float diff = newPos.Y - body.Position.Y;
            if (Math.Abs(diff) < EPSILON) { diff = 0f; }
            accumulator += diff;

            if (Math.Abs(accumulator) >= SPACE_BETWEEN_GROUND)
            {
                Boolean accumulatorSignPlus = (accumulator > 0f);

                #region Number of ground to bypass
                int nbGround = 0;
                accumulator = Math.Abs(accumulator);
                while (accumulator > 0f)
                {
                    nbGround++;
                    accumulator -= SPACE_BETWEEN_GROUND;
                }
                #endregion

                #region Set new ground
                if (accumulatorSignPlus)
                    ground = ChangeGround(body, geom, currentGround, nbGround);
                else
                    ground = ChangeGround(body, geom, currentGround, -nbGround);
                #endregion

                // set new position
                body.Position = new Vector2(newPos.X, ground.Y - ((height + GROUND_HEIGHT) / 2f));
                // clear accumulator
                accumulator = 0f;
            }
            else
            {
                body.Position = new Vector2(newPos.X, body.Position.Y);
            }
            body.ResetDynamics(body.Position.X, body.Position.Y);
            return ground;
        }
        #endregion
    }
}
